/*
*    UNIVERSIDADE DE SÃO PAULO.
*    Author: Marco Aurélio Gerosa (gerosa@ime.usp.br)
*    This project was/is sponsored by RNP and FAPESP.
*
*    This file is part of Groupware Workbench (http://www.groupwareworkbench.org.br).
*
*    Groupware Workbench is free software: you can redistribute it and/or modify
*    it under the terms of the GNU Lesser General Public License as published by
*    the Free Software Foundation, either version 3 of the License, or
*    (at your option) any later version.
*
*    Groupware Workbench is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*    GNU Lesser General Public License for more details.
*
*    You should have received a copy of the GNU Lesser General Public License
*    along with Swift.  If not, see <http://www.gnu.org/licenses/>.
*/
package br.org.groupwareworkbench.collablet.coop.recommend;

import br.org.groupwareworkbench.core.xmlconfig.ClassInfo;
import br.org.groupwareworkbench.core.xmlconfig.XmlSpecificConfig;
import br.org.groupwareworkbench.core.framework.AbstractBusiness;
import br.org.groupwareworkbench.core.framework.Collablet;
import br.org.groupwareworkbench.core.framework.annotations.ComponentInfo;
import br.org.groupwareworkbench.core.framework.annotations.DefaultProperty;

import java.io.Serializable;

import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import org.dom4j.Element;

@ComponentInfo(
    version="0.1",
    configurationURL="/groupware-workbench/recommend/{recommendMgr}/configure",
    defaultProperties={
        @DefaultProperty(name="numRelated", defaultValue="5"),
        @DefaultProperty(name="minSimilarityThreshold", defaultValue="0.0")
    }
)
public class RecommendMgrInstance extends AbstractBusiness {

    private XmlSpecificConfig xmlConfig;

    public RecommendMgrInstance(Collablet collablet) {
        super(collablet);
    }

    // --------------------- Operações ----------------------

    public void saveRecommend(Recommend recommend) {
        recommend.setCollablet(getCollablet());
        recommend.save();
    }

    public int getNumRelated() {
        return Integer.parseInt(this.getCollablet().getProperty("numRelated"));
    }

    public void setNumRelated(int numRelated) {
        getCollablet().setProperty("numRelated", String.valueOf(numRelated));
        getCollablet().save();
    }

    // Usado em widgets.
    public List<Recommend> getRelated(Object entity) {
        return Recommend.listRelated(entity, getNumRelated(), getCollablet());
    }

    private void makeTempXmlConfig() {
        if (xmlConfig != null) return;

        org.dom4j.Document document = org.dom4j.DocumentHelper.createDocument();
        Element rootElement = document.addElement("specific-configuration");

        Element recommendNews = rootElement.addElement("classType");
        recommendNews.addAttribute("className", "br.org.groupwareworkbench.eureka.knowledge.Knowledge");

        Element methodGetTitle = recommendNews.addElement("method");
        methodGetTitle.addAttribute("name", "getEscapedCause");
        methodGetTitle.addAttribute("enabled", "true");

        Element methodGetSolution = recommendNews.addElement("method");
        methodGetSolution.addAttribute("name", "getEscapedSolution");
        methodGetSolution.addAttribute("enabled", "true");

        this.xmlConfig = new XmlSpecificConfig(document);
    }

    private static class SimilarityComparator implements Comparator<Recommend>, Serializable {

        private static final long serialVersionUID = -756647488461937227L;

        @Override
        public int compare(Recommend o1, Recommend o2) {

            // Since higher similarities comes first, they should be sorted in decreasing order.
            // So, we return -1 if first is smaller than second and 1 if is greater. The reverse
            // of what is commonly used.
            double diff = o1.getSimilarity() - o2.getSimilarity();
            return diff == 0.0 ? 0 : diff > 0.0 ? -1 : 1;
        }
    }

    public <E extends Object> void generateRelated(List<E> targets, E from) {
        double threshold = Double.parseDouble(this.getCollablet().getProperty("minSimilarityThreshold"));
        makeTempXmlConfig();

        SortedSet<Recommend> relatedList = new TreeSet<Recommend>(new SimilarityComparator());

        for (Recommend rec : getRelated(from)) {
            rec.delete();
        }
        for (E to : targets) {
            if (from.equals(to)) continue;
            Recommend recommend = new Recommend(xmlConfig, from, to);
            if (recommend.getSimilarity() < threshold) continue;

            relatedList.add(recommend);
            if (relatedList.size() > getNumRelated())  {
                relatedList.remove(relatedList.last());
            }
        }
        for (Recommend rec : relatedList) {
            rec.save();
        }
    }

    public <E extends Object> void calculeAll(List<E> currentList) {
        makeTempXmlConfig();

        List<E> toList = currentList;
        int i = 1;

        for (E from : currentList) {
            generateRelated(toList.subList(i, toList.size()), from);
            i++;
            System.out.println("num de registro : " + i);
        }
    }

    public synchronized XmlSpecificConfig getXmlConfig() {
        return xmlConfig;
    }

    public Collection<ClassInfo> listClasses() {
        return xmlConfig.listClasses();
    }

    public void setEnableds(List<String> enabledMethodList) {
        xmlConfig.setEnableds(enabledMethodList);
    }
}
